﻿#pragma once
#include <cmath>

namespace GLMath
{
    const float EPSILON = 0.000001f;
    const float DEG2RAD = 3.141593f / 180.0f;
    const float RAD2DEG = 180.0f / 3.141593f;

#pragma region 2D Vector
    struct Vector2
    {
        float x;
        float y;

        // ctors
        Vector2() : x(0), y(0) {};
        Vector2(float x, float y) : x(x), y(y) {};

        // utils functions
        void        set(float x, float y);
        float       length() const;                         //
        float       distance(const Vector2& vec) const;     // distance between two vectors
        Vector2&    normalize();                            //
        float       dot(const Vector2& vec) const;          // dot product
        bool        equal(const Vector2& vec, float e = EPSILON) const; // compare with epsilon

        // operators
        Vector2     operator-() const;                      // unary operator (negate)
        Vector2     operator+(const Vector2& rhs) const;    // add rhs
        Vector2     operator-(const Vector2& rhs) const;    // subtract rhs
        Vector2&    operator+=(const Vector2& rhs);         // add rhs and update this object
        Vector2&    operator-=(const Vector2& rhs);         // subtract rhs and update this object
        Vector2     operator*(const float scale) const;     // scale
        Vector2&    operator*=(const float scale);          // scale and update this object
        bool        operator==(const Vector2& rhs) const;   // exact compare, no epsilon
        bool        operator!=(const Vector2& rhs) const;   // exact compare, no epsilon

        friend Vector2 operator*(const float a, const Vector2 vec);
    };

    ///////////////////////////////////////////////////////////////////////////////
    // inline functions for Vector2
    ///////////////////////////////////////////////////////////////////////////////
    inline Vector2 Vector2::operator-() const {
        return Vector2(-x, -y);
    }

    inline Vector2 Vector2::operator+(const Vector2& rhs) const {// right hand side
        return Vector2(x + rhs.x, y + rhs.y);
    }

    inline Vector2 Vector2::operator-(const Vector2& rhs) const {
        return Vector2(x - rhs.x, y - rhs.y);
    }

    inline Vector2& Vector2::operator+=(const Vector2& rhs) {
        x += rhs.x; y += rhs.y; return *this;
    }

    inline Vector2& Vector2::operator-=(const Vector2& rhs) {
        x -= rhs.x; y -= rhs.y; return *this;
    }

    inline Vector2 Vector2::operator*(const float a) const {
        return Vector2(x * a, y * a);
    }

    inline Vector2& Vector2::operator*=(const float a) {
        x *= a; y *= a; return *this;
    }

    inline bool Vector2::operator==(const Vector2& rhs) const {
        return equal(rhs, EPSILON);
    }

    inline bool Vector2::operator!=(const Vector2& rhs) const {
        return !equal(rhs);
    }

    inline void Vector2::set(float x, float y) {
        this->x = x;    this->y = y;
    }

    inline float Vector2::length() const {
        return sqrtf(x * x + y * y);
    }

    inline float Vector2::distance(const Vector2& vec) const {
        return sqrtf((vec.x -x) *(vec.x - x) + (vec.y - y) * (vec.y - y));
    }

    inline Vector2& Vector2::normalize() {
        float xxyy = x * x + y * y;

        float invLengh = 1.0f / sqrtf(xxyy);
        x *= invLengh;
        y *= invLengh;
        return *this;
    }

    inline float Vector2::dot(const Vector2& rhs) const {
        return x * rhs.x + y * rhs.y;
    }

    inline bool Vector2::equal(const Vector2& rhs, float epsilon) const {
        return fabs(x - rhs.x) < epsilon && fabs( y - rhs.y) < epsilon;
    }

    inline Vector2 operator*(const float a, const Vector2 vec) {
        return Vector2(vec.x * a, vec.y * a);
    }

    // END OF VECTOR2 /////////////////////////////////////////////////////////////

#pragma endregion

#pragma region 3D vector
    struct Vector3
    {
        float x;
        float y;
        float z;

        // ctors
        Vector3() : x(0), y(0), z(0) {};
        Vector3(float x, float y, float z) : x(x), y(y), z(z) {};

        // utils functions
        void        set(float x, float y, float z);
        float       length() const;                         //
        float       distance(const Vector3& vec) const;     // distance between two vectors
        float       angle(const Vector3& vec) const;        // angle between two vectors
        Vector3&    normalize();                            //
        float       dot(const Vector3& vec) const;          // dot product
        Vector3     cross(const Vector3& vec) const;        // cross product
        bool        equal(const Vector3& vec, float e = EPSILON) const; // compare with epsilon

        // operators
        Vector3     operator-() const;                      // unary operator (negate)
        Vector3     operator+(const Vector3& rhs) const;    // add rhs
        Vector3     operator-(const Vector3& rhs) const;    // subtract rhs
        Vector3&    operator+=(const Vector3& rhs);         // add rhs and update this object
        Vector3&    operator-=(const Vector3& rhs);         // subtract rhs and update this object
        Vector3     operator*(const float scale) const;     // scale
        Vector3&    operator*=(const float scale);          // scale and update this object
        bool        operator==(const Vector3& rhs) const;   // exact compare, no epsilon
        bool        operator!=(const Vector3& rhs) const;   // exact compare, no epsilon

        friend Vector3 operator*(const float a, const Vector3 vec);
    };

    ///////////////////////////////////////////////////////////////////////////////
    // inline functions for Vector3
    ///////////////////////////////////////////////////////////////////////////////
    inline Vector3 Vector3::operator-() const {
        return Vector3(-x, -y, -z);
    }

    inline Vector3 Vector3::operator+(const Vector3& rhs) const {
        return Vector3(x + rhs.x, y + rhs.y, z + rhs.z);
    }

    inline Vector3 Vector3::operator-(const Vector3& rhs) const {
        return Vector3(x - rhs.x, y - rhs.y, z - rhs.z);
    }

    inline Vector3& Vector3::operator+=(const Vector3& rhs) {
        x += rhs.x; y += rhs.y; z += rhs.z; return *this;
    }

    inline Vector3& Vector3::operator-=(const Vector3& rhs) {
        x -= rhs.x; y -= rhs.y; z -= rhs.z; return *this;
    }

    inline Vector3 Vector3::operator*(const float a) const {
        return Vector3(x * a, y * a, z * a);
    }

    inline Vector3& Vector3::operator*=(const float a) {
        x *= a; y *= a; z *= a; return *this;
    }

    inline bool Vector3::operator==(const Vector3& rhs) const {
        return equal(rhs, EPSILON);
    }

    inline bool Vector3::operator!=(const Vector3& rhs) const {
        return !equal(rhs, EPSILON);
    }

    inline void Vector3::set(float x, float y, float z) {
        this->x = x; this->y = y; this->z = z;
    }

    inline float Vector3::length() const {
        return sqrtf(x * x + y * y + z * z);
    }

    inline float Vector3::distance(const Vector3& vec) const {
        return sqrtf((vec.x - x) * (vec.x - x) + (vec.y - y) * (vec.y - y) + (vec.z - z) * (vec.z - z));
    }

    inline float Vector3::angle(const Vector3& vec) const {
        // return angle between [0 - 180]
        float l1 = this->length();
        float l2 = vec.length();

        float d = this->dot(vec);
        float angle = acosf(d / (l1 * l2)) / 3.141592f * 180.0f;
        return angle;
    }

    inline Vector3& Vector3::normalize() {
        float xxyyzz = x * x + y * y + z * z;

        float invLength = 1.0f / sqrtf(xxyyzz);
        x *= invLength;
        y *= invLength;
        z *= invLength;
        return *this;
    }

    inline float Vector3::dot(const Vector3& rhs) const {
        return (x * rhs.x + y * rhs.y + z * rhs.z);
    }

    inline Vector3 Vector3::cross(const Vector3& rhs) const {
        // TODO
    }

    inline bool Vector3::equal(const Vector3& rhs, float epsilon) const {
        return fabs(x - rhs.x) < epsilon && fabs(y - rhs.y) < epsilon && fabs(z - rhs.z) < epsilon;
    }

    inline Vector3 operator*(const float a, const Vector3 vec) {
        return Vector3(a * vec.x, a * vec.y, a * vec.z);
    }
    // END OF VECTOR3 /////////////////////////////////////////////////////////////

#pragma endregion

#pragma region 4D vector
    struct Vector4
    {
        float x;
        float y;
        float z;
        float w;

        // ctors
        Vector4() : x(0), y(0), z(0), w(0) {};
        Vector4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {};

        // utils functions
        void        set(float x, float y, float z, float w);
        float       length() const;                         //
        float       distance(const Vector4& vec) const;     // distance between two vectors
        Vector4&    normalize();                            //
        float       dot(const Vector4& vec) const;          // dot product
        bool        equal(const Vector4& vec, float e = EPSILON) const; // compare with epsilon

        // operators
        Vector4     operator-() const;                      // unary operator (negate)
        Vector4     operator+(const Vector4& rhs) const;    // add rhs
        Vector4     operator-(const Vector4& rhs) const;    // subtract rhs
        Vector4&    operator+=(const Vector4& rhs);         // add rhs and update this object
        Vector4&    operator-=(const Vector4& rhs);         // subtract rhs and update this object
        Vector4     operator*(const float scale) const;     // scale
        Vector4&    operator*=(const float scale);          // scale and update this object
        bool        operator==(const Vector4& rhs) const;   // exact compare, no epsilon
        bool        operator!=(const Vector4& rhs) const;   // exact compare, no epsilon

        friend Vector4 operator*(const float a, const Vector4 vec);
    };

    ///////////////////////////////////////////////////////////////////////////////
    // inline functions for Vector4
    ///////////////////////////////////////////////////////////////////////////////
    inline Vector4 Vector4::operator-() const {
        return Vector4(-x, -y, -z, -w);
    }

    inline Vector4 Vector4::operator+(const Vector4& rhs) const {
        return Vector4(x + rhs.x, y + rhs.y, z + rhs.z, w + rhs.w);
    }

    inline Vector4 Vector4::operator-(const Vector4& rhs) const {
        return Vector4(x - rhs.x, y - rhs.y, z - rhs.z, w - rhs.w);
    }

    inline Vector4& Vector4::operator+=(const Vector4& rhs) {
        x += rhs.x; y += rhs.y; z += rhs.z; w += rhs.w; return *this;
    }

    inline Vector4& Vector4::operator-=(const Vector4& rhs) {
        x -= rhs.x; y -= rhs.y; z -= rhs.z; w -= rhs.w; return *this;
    }

    inline Vector4 Vector4::operator*(const float a) const {
        return Vector4(x * a, y * a, z * a, w * a);
    }

    inline Vector4& Vector4::operator*=(const float a) {
        x *= a; y *= a; z *= a; w *= a; return *this;
    }

    inline bool Vector4::operator==(const Vector4& rhs) const {
        return equal(rhs, EPSILON);
    }

    inline bool Vector4::operator!=(const Vector4& rhs) const {
        return !equal(rhs, EPSILON);
    }

    inline void Vector4::set(float x, float y, float z, float w) {
        this->x = x; this->y = y; this->z = z; this->w = w;
    }

    inline float Vector4::length() const {
        return sqrtf(x * x + y * y + z * z + w * w);
    }

    inline float Vector4::distance(const Vector4& vec) const {
        return sqrtf((vec.x - x) * (vec.x - x) + (vec.y - y) * (vec.y - y) + (vec.z - z) * (vec.z - z) + (vec.w - w) * (vec.w - w));
    }

    inline Vector4& Vector4::normalize() {
        float xxyyzz = x * x + y * y + z * z;

        float invLength = 1.0f / sqrtf(xxyyzz);
        x *= invLength;
        y *= invLength;
        z *= invLength;
        return *this;
    }

    inline float Vector4::dot(const Vector4& rhs) const {
        return (x * rhs.x + y * rhs.y + z * rhs.z + w * rhs.w);
    }

    inline bool Vector4::equal(const Vector4& rhs, float epsilon) const {
        return fabs(x - rhs.x) < epsilon && fabs(y - rhs.y) < epsilon &&
            fabs(z - rhs.z) < epsilon && fabs(w - rhs.w) < epsilon;
    }

    inline Vector4 operator*(const float a, const Vector4 vec) {
        return Vector4(a * vec.x, a * vec.y, a * vec.z, a * vec.w);
    }
    // END OF VECTOR4 /////////////////////////////////////////////////////////////

#pragma endregion
}